// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming

using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers;
using MQTTnet.Server;
using MQTTnet.Server.EnhancedAuthentication;

namespace MQTTnet.Samples.Client;

public static class Client_Connection_Samples
{
    const string mosquitto_org = @"
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE-----
";

    public static async Task Clean_Disconnect()
    {
        /*
         * This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
         * to the server and close the connection afterward.
         *
         * See sample _Connect_Client_ for more details.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
        await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        // This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
        // connection is closed in a "not clean" way. See MQTT specification for more details.
        await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
    }

    public static async Task Connect()
    {
        /*
         * This sample creates a simple MQTT client and connects to a public broker.
         *
         * Always dispose the client when it is no longer used.
         * The default version of MQTT is 3.1.1.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        // Use builder classes where possible in this project.
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();

        // This will throw an exception if the server is not available.
        // The result from this message returns additional data which was sent
        // from the server. Please refer to the MQTT protocol specification for details.
        var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        Console.WriteLine("The MQTT client is connected.");

        response.DumpToConsole();

        // Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
        // gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
        var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();

        await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
    }

    public static async Task Connect_Using_Enhanced_Authentication()
    {
        /*
         * This sample uses enhanced authentication (Kerberos) when creating the connection.
         */


        /*
         * Server part...
         */

        var mqttServerFactory = new MqttServerFactory();

        var serverOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
        var server = mqttServerFactory.CreateMqttServer(serverOptions);

        server.ValidatingConnectionAsync += async args =>
        {
            if (args.AuthenticationMethod == "GS2-KRB5")
            {
                var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken);

                Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");

                var authOptions = mqttServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();

                result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken);

                Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");

                args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();

                // Authentication DONE!
                args.ReasonCode = MqttConnectReasonCode.Success; // Also the default!
            }
            else
            {
                args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
            }
        };

        await server.StartAsync();

        /*
         * Client part...
         */

        var mqttClientFactory = new MqttClientFactory();

        // Use Kerberos sample from the MQTT RFC.
        var kerberosAuthenticationHandler = new SampleClientKerberosAuthenticationHandler();

        var clientOptions = mqttClientFactory.CreateClientOptionsBuilder()
            .WithTcpServer("localhost")
            .WithProtocolVersion(MqttProtocolVersion.V500)
            .WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler)
            .WithEnhancedAuthentication("GS2-KRB5")
            .Build();

        var client = mqttClientFactory.CreateMqttClient();

        var result = await client.ConnectAsync(clientOptions);

        Console.WriteLine($"Client connect result: {result.ResultCode}");
    }

    public static async Task Connect_Using_MQTTv5()
    {
        /*
         * This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
         *
         * This is a modified version of the sample _Connect_Client_! See other sample for more details.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();

        // In MQTTv5 the response contains much more information.
        var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        Console.WriteLine("The MQTT client is connected.");

        response.DumpToConsole();
    }

    public static async Task Connect_Using_TLS_1_2()
    {
        /*
         * This sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
         *
         * This is a modified version of the sample _Connect_Client_! See other sample for more details.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
            .WithTlsOptions(
                o =>
                {
                    // The used public broker sometimes has invalid certificates. This sample accepts all
                    // certificates. This should not be used in live environments.
                    o.WithCertificateValidationHandler(_ => true);

                    // The default value is determined by the OS. Set manually to force version.
                    o.WithSslProtocols(SslProtocols.Tls12);
                })
            .Build();

        using var timeout = new CancellationTokenSource(5000);
        await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);

        Console.WriteLine("The MQTT client is connected.");
    }

    public static async Task Connect_Using_TLS_Encryption()
    {
        /*
         * This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
         *
         * This is a modified version of the sample _Connect_Client_! See other sample for more details.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
            .WithTlsOptions(
                o => o.WithCertificateValidationHandler(
                    // The used public broker sometimes has invalid certificates. This sample accepts all
                    // certificates. This should not be used in live environments.
                    _ => true))
            .Build();

        // In MQTTv5 the response contains much more information.
        using var timeout = new CancellationTokenSource(5000);
        var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);

        Console.WriteLine("The MQTT client is connected.");

        response.DumpToConsole();
    }

    public static async Task Connect_Using_TLS_With_CA_File()
    {
        var mqttFactory = new MqttClientFactory();

        var caChain = new X509Certificate2Collection();
        caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
            .WithTlsOptions(new MqttClientTlsOptionsBuilder().WithTrustChain(caChain).Build())
            .Build();

        var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
        Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
    }

    public static async Task Connect_Using_WebSockets()
    {
        /*
         * This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
         *
         * This is a modified version of the sample _Connect_Client_! See other sample for more details.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();

        var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        Console.WriteLine("The MQTT client is connected.");

        response.DumpToConsole();
    }

    public static async Task Connect_With_Amazon_AWS()
    {
        /*
         * This sample creates a simple MQTT client and connects to an Amazon Web Services broker.
         *
         * The broker requires special settings which are set here.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
            // Disabling packet fragmentation is very important!
            .WithoutPacketFragmentation()
            .Build();

        await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        Console.WriteLine("The MQTT client is connected.");

        await mqttClient.DisconnectAsync();
    }

    public static async Task Disconnect_Clean()
    {
        /*
         * This sample disconnects from the server with sending a DISCONNECT packet.
         * This way of disconnecting is treated as a clean disconnect which will not
         * trigger sending the last will etc.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();

        await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        // Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
        // Using a reason code requires MQTT version 5.0.0!
        await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
    }

    public static async Task Disconnect_Non_Clean()
    {
        /*
         * This sample disconnects from the server without sending a DISCONNECT packet.
         * This way of disconnecting is treated as a non-clean disconnect which will
         * trigger sending the last will etc.
         */

        var mqttFactory = new MqttClientFactory();

        var mqttClient = mqttFactory.CreateMqttClient();

        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();

        await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        // Calling _Dispose_ or use of a _using_ statement will close the transport connection
        // without sending a DISCONNECT packet to the server.
        mqttClient.Dispose();
    }

    public static async Task Inspect_Certificate_Validation_Errors()
    {
        /*
         * This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
         * including the reason for that status.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
            .WithTlsOptions(
                o =>
                {
                    o.WithCertificateValidationHandler(
                        eventArgs =>
                        {
                            eventArgs.Certificate.Subject.DumpToConsole();
                            eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
                            eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
                            eventArgs.Chain.ChainStatus.DumpToConsole();
                            eventArgs.SslPolicyErrors.DumpToConsole();
                            return true;
                        });
                })
            .Build();

        // In MQTTv5 the response contains much more information.
        using var timeout = new CancellationTokenSource(5000);
        await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
    }

    public static async Task Ping_Server()
    {
        /*
         * This sample sends a PINGREQ packet to the server and waits for a reply.
         *
         * This is only supported in MQTTv5.0.0+.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();

        await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

        // This will throw an exception if the server does not reply.
        await mqttClient.PingAsync(CancellationToken.None);

        Console.WriteLine("The MQTT server replied to the ping request.");
    }

    public static async Task Reconnect_Using_Event()
    {
        /*
         * This sample shows how to reconnect when the connection was dropped.
         * This approach uses one of the events from the client.
         * This approach has a risk of deadlocks! Consider using the timer approach (see sample).
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();

        mqttClient.DisconnectedAsync += async e =>
        {
            if (e.ClientWasConnected)
            {
                // Use the current options as the new options.
                await mqttClient.ConnectAsync(mqttClient.Options);
            }
        };

        await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
    }

    public static void Reconnect_Using_Timer()
    {
        /*
         * This sample shows how to reconnect when the connection was dropped.
         * This approach uses a custom Task/Thread which will monitor the connection status.
         * This is the recommended way but requires more custom code!
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();

        _ = Task.Run(
            async () =>
            {
                // User proper cancellation and no while(true).
                while (true)
                {
                    try
                    {
                        // This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
                        if (!await mqttClient.TryPingAsync())
                        {
                            await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);

                            // Subscribe to topics when session is clean etc.
                            Console.WriteLine("The MQTT client is connected.");
                        }
                    }
                    catch
                    {
                        // Handle the exception properly (logging etc.).
                    }
                    finally
                    {
                        // Check the connection state every 5 seconds and perform a reconnect if required.
                        await Task.Delay(TimeSpan.FromSeconds(5));
                    }
                }
            });

        Console.WriteLine("Press <Enter> to exit");
        Console.ReadLine();
    }

    public static async Task Timeout()
    {
        /*
         * This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
         *
         * This is a modified version of the sample _Connect_Client_! See other sample for more details.
         */

        var mqttFactory = new MqttClientFactory();

        using var mqttClient = mqttFactory.CreateMqttClient();
        var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();

        try
        {
            using var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1));
            await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Timeout while connecting.");
        }
    }

    class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
    {
        public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
        {
            if (eventArgs.AuthenticationMethod != "GS2-KRB5")
            {
                throw new InvalidOperationException("Wrong authentication method");
            }

            var sendOptions = new SendMqttEnhancedAuthenticationDataOptions
            {
                Data = "initial context token"u8.ToArray()
            };

            await eventArgs.SendAsync(sendOptions);

            var response = await eventArgs.ReceiveAsync(CancellationToken.None);

            Console.WriteLine($"Received AUTH data from server: {Encoding.UTF8.GetString(response.AuthenticationData)}");

            // No further data is required, but we have to fulfil the exchange.
            sendOptions = new SendMqttEnhancedAuthenticationDataOptions
            {
                Data = []
            };

            await eventArgs.SendAsync(sendOptions, CancellationToken.None);
        }
    }
}